using System.Windows.Controls.Primitives;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using System;
using System.Diagnostics;
using System.Collections.Specialized;

namespace TagFlo
{
    class VirtualizingTilePanel : VirtualizingPanel, IScrollInfo
    {
        public VirtualizingTilePanel()
        {
            // For use in the IScrollInfo implementation
            this.RenderTransform = _trans;
        }

        // Dependency property that controls the size of the child elements
        public static readonly DependencyProperty ChildSizeProperty
           = DependencyProperty.RegisterAttached("ChildSize", typeof(double), typeof(VirtualizingTilePanel),
              new FrameworkPropertyMetadata(200.0d, FrameworkPropertyMetadataOptions.AffectsMeasure |
              FrameworkPropertyMetadataOptions.AffectsArrange));

        // Accessor for the child size dependency property
        public double ChildSize
        {
            get { return (double)GetValue(ChildSizeProperty); }
            set { SetValue(ChildSizeProperty, value); }
        }

        /// <summary>
        /// Measure the children
        /// </summary>
        /// <param name="availableSize">Size available</param>
        /// <returns>Size desired</returns>
        /// 

        /*
        protected override Size MeasureOverride(Size constraint)
        {
            int num;
            double width;
            UIElementCollection internalChildren = base.InternalChildren;
            Size size = new Size();
            Size availableSize = constraint;
            bool flag = this.Orientation == Orientation.Horizontal;
            int num2 = -1;
            bool flag2 = this.IsScrolling && EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal);
            if (flag2)
            {
                EventTrace.EventProvider.TraceEvent(EventTrace.GENERICSTRINGGUID, 1, "VirtualizingStackPanel :MeasureOverride");
            }
            IItemContainerGenerator generator = base.Generator;
            ItemsControl itemsOwner = ItemsControl.GetItemsOwner(this);
            int numberOfItems = (itemsOwner != null) ? itemsOwner.Items.Count : 0;
            bool flag3 = (itemsOwner != null) ? GetIsVirtualizing(itemsOwner) : true;
            if (flag)
            {
                availableSize.Width = double.PositiveInfinity;
                if (this.IsScrolling && this.CanVerticallyScroll)
                {
                    availableSize.Height = double.PositiveInfinity;
                }
                num = this.IsScrolling ? CoerceOffsetToInteger(this._scrollData._offset.X, numberOfItems) : 0;
                width = constraint.Width;
            }
            else
            {
                availableSize.Height = double.PositiveInfinity;
                if (this.IsScrolling && this.CanHorizontallyScroll)
                {
                    availableSize.Width = double.PositiveInfinity;
                }
                num = this.IsScrolling ? CoerceOffsetToInteger(this._scrollData._offset.Y, numberOfItems) : 0;
                width = constraint.Height;
            }
            GeneratorPosition position = this.IndexToGeneratorPositionForStart(flag3 ? num : 0, out this._firstVisibleChildIndex);
            int index = this._firstVisibleChildIndex;
            bool flag4 = true;
            this._visibleCount = 0;
            if (numberOfItems > 0)
            {
                this._afterTrail = 0;
                using (IDisposable disposable = generator.StartAt(position, GeneratorDirection.Forward, true))
                {
                    int num7 = flag3 ? num : 0;
                    int num8 = numberOfItems;
                    while (num7 < num8)
                    {
                        double height;
                        bool flag5;
                        UIElement child = generator.GenerateNext(out flag5) as UIElement;
                        if (child == null)
                        {
                            goto Label_0350;
                        }
                        if (flag5)
                        {
                            if (index >= internalChildren.Count)
                            {
                                VirtualizingPanel.AddInternalChild(internalChildren, child);
                            }
                            else
                            {
                                VirtualizingPanel.InsertInternalChild(internalChildren, index, child);
                            }
                            generator.PrepareItemContainer(child);
                        }
                        index++;
                        this._visibleCount++;
                        if (child.MeasureDirty && (this._scrollData != null))
                        {
                            this._scrollData._maxDesiredSize = new Size();
                        }
                        child.Measure(availableSize);
                        Size desiredSize = child.DesiredSize;
                        if (flag)
                        {
                            size.Width += desiredSize.Width;
                            size.Height = Math.Max(size.Height, desiredSize.Height);
                            height = desiredSize.Width;
                        }
                        else
                        {
                            size.Width = Math.Max(size.Width, desiredSize.Width);
                            size.Height += desiredSize.Height;
                            height = desiredSize.Height;
                        }
                        if ((this.IsScrolling && (num2 == -1)) && (num7 >= num))
                        {
                            width -= height;
                            if (DoubleUtil.LessThanOrClose(width, 0.0))
                            {
                                num2 = num7;
                            }
                        }
                        if (((this.IsScrolling && flag3) && (num7 > num)) && ((flag && ((size.Width - desiredSize.Width) > constraint.Width)) || (!flag && ((size.Height - desiredSize.Height) > constraint.Height))))
                        {
                            this._afterTrail++;
                            if ((this._afterTrail >= 5) || Keyboard.IsFocusable(child))
                            {
                                flag4 = false;
                                goto Label_0350;
                            }
                        }
                        num7++;
                    }
                }
            }
        Label_0350:
            this._visibleStart = num;
            if (this.IsScrolling)
            {
                Size viewport = constraint;
                Vector offset = this._scrollData._offset;
                bool inChildInvalidateMeasure = false;
                if (this.ScrollOwner != null)
                {
                    inChildInvalidateMeasure = this.ScrollOwner.InChildInvalidateMeasure;
                    this.ScrollOwner.InChildInvalidateMeasure = false;
                }
                Size extent = new Size();
                if (flag)
                {
                    extent.Width = numberOfItems;
                    extent.Height = inChildInvalidateMeasure ? Math.Max(size.Height, this._scrollData._extent.Height) : size.Height;
                }
                else
                {
                    extent.Width = inChildInvalidateMeasure ? Math.Max(size.Width, this._scrollData._extent.Width) : size.Width;
                    extent.Height = numberOfItems;
                }
                if (num2 == -1)
                {
                    num2 = numberOfItems - 1;
                }
                if (flag4)
                {
                    index = flag3 ? this._firstVisibleChildIndex : num;
                    while (index > 0)
                    {
                        if (!this.PreviousChildIsGenerated(index))
                        {
                            this.GeneratePreviousChild(index, availableSize);
                            index++;
                        }
                        else if (index <= this._firstVisibleChildIndex)
                        {
                            internalChildren[index - 1].Measure(availableSize);
                        }
                        double num9 = width;
                        Size size6 = internalChildren[index - 1].DesiredSize;
                        if (flag)
                        {
                            num9 -= size6.Width;
                        }
                        else
                        {
                            num9 -= size6.Height;
                        }
                        if (DoubleUtil.LessThan(num9, 0.0))
                        {
                            break;
                        }
                        if (flag)
                        {
                            size.Width += size6.Width;
                            size.Height = Math.Max(size.Height, size6.Height);
                        }
                        else
                        {
                            size.Width = Math.Max(size.Width, size6.Width);
                            size.Height += size6.Height;
                        }
                        index--;
                        width = num9;
                    }
                    if ((index < this._firstVisibleChildIndex) || !flag3)
                    {
                        this._firstVisibleChildIndex = index;
                    }
                    this._visibleStart = num = (this.GetGeneratedCount(internalChildren) == 0) ? 0 : this.GetGeneratedIndex(this._firstVisibleChildIndex);
                }
                this.EnsureTopCapGenerated(availableSize);
                int num10 = numberOfItems;
                int num11 = num2 - num;
                if ((num11 == 0) || DoubleUtil.GreaterThanOrClose(width, 0.0))
                {
                    num11++;
                }
                if (flag)
                {
                    this._scrollData._physicalViewport = viewport.Width;
                    viewport.Width = num11;
                    offset.X = num;
                    offset.Y = Math.Max(0.0, Math.Min(offset.Y, extent.Height - viewport.Height));
                    if ((num10 > num11) && !double.IsPositiveInfinity(constraint.Width))
                    {
                        size.Width = constraint.Width;
                    }
                }
                else
                {
                    this._scrollData._physicalViewport = viewport.Height;
                    viewport.Height = num11;
                    offset.Y = num;
                    offset.X = Math.Max(0.0, Math.Min(offset.X, extent.Width - viewport.Width));
                    if ((num10 > num11) && !double.IsPositiveInfinity(constraint.Height))
                    {
                        size.Height = constraint.Height;
                    }
                }
                size.Width = Math.Min(size.Width, constraint.Width);
                size.Height = Math.Min(size.Height, constraint.Height);
                this._scrollData._maxDesiredSize.Width = Math.Max(size.Width, this._scrollData._maxDesiredSize.Width);
                this._scrollData._maxDesiredSize.Height = Math.Max(size.Height, this._scrollData._maxDesiredSize.Height);
                size = this._scrollData._maxDesiredSize;
                this.VerifyScrollingData(viewport, extent, offset);
            }
            if (flag3)
            {
                this.EnsureCleanupOperation(false);
            }
            if (flag2)
            {
                EventTrace.EventProvider.TraceEvent(EventTrace.GENERICSTRINGGUID, 2, "VirtualizingStackPanel :MeasureOverride");
            }
            return size;
        }

*/ 

 


        protected override Size MeasureOverride(Size availableSize)
        {
            UpdateScrollInfo(availableSize);

            // Figure out range that's visible based on layout algorithm
            int firstVisibleItemIndex, lastVisibleItemIndex;
            GetVisibleRange(out firstVisibleItemIndex, out lastVisibleItemIndex);

            // We need to access InternalChildren before the generator to work around a bug
            UIElementCollection children = this.InternalChildren;
            IItemContainerGenerator generator = this.ItemContainerGenerator;

            // Get the generator position of the first visible data item
            GeneratorPosition startPos = generator.GeneratorPositionFromIndex(firstVisibleItemIndex);


            // Get index where we'd insert the child for this position. If the item is realized
            // (position.Offset == 0), it's just position.Index, otherwise we have to add one to
            // insert after the corresponding child
            int childIndex = (startPos.Offset == 0) ? startPos.Index : startPos.Index + 1;

            using (IDisposable disposable = generator.StartAt(startPos, GeneratorDirection.Forward, true))
            {
                   for (int itemIndex = firstVisibleItemIndex; itemIndex <= lastVisibleItemIndex; ++itemIndex, ++childIndex)
                    {
                        try
                        {
                            bool newlyRealized;
                            Size size = new Size(Double.PositiveInfinity, Double.PositiveInfinity);

                            // Get or create the child
                            UIElement child = generator.GenerateNext(out newlyRealized) as UIElement;
                            if (child != null)
                            {
                                if (newlyRealized)
                                {
                                    // Figure out if we need to insert the child at the end or somewhere in the middle
                                    if (childIndex >= children.Count)
                                    {
                                        base.AddInternalChild(child);
                                    }
                                    else
                                    {
                                        base.InsertInternalChild(childIndex, child);
                                    }
                                    generator.PrepareItemContainer(child);
                                }
                                else
                                {
                                    // The child has already been created, let's be sure it's in the right spot
                                   // Debug.Assert(child == children[childIndex], "Wrong child was generated");
                                }

                                // Measurements will depend on layout algorithm
                                // child.Measure(GetChildSize());
                                child.Measure(size);
                            }

                        }
                        catch (Exception ex)
                        {
                            Trace.WriteLine(ex.Message);
                        }
                    }
                        
            
                
            }

            // Note: this could be deferred to idle time for efficiency
            CleanUpItems(firstVisibleItemIndex, lastVisibleItemIndex);

            if (double.IsInfinity(availableSize.Height) || double.IsInfinity(availableSize.Width))
                return new Size(500, 500);
            else
                return availableSize;

        }

        /// <summary>
        /// Arrange the children
        /// </summary>
        /// <param name="finalSize">Size available</param>
        /// <returns>Size used</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            IItemContainerGenerator generator = this.ItemContainerGenerator;

            UpdateScrollInfo(finalSize);

            for (int i = 0; i < this.Children.Count; i++)
            {
                UIElement child = this.Children[i];

                // Map the child offset to an item offset
                int itemIndex = generator.IndexFromGeneratorPosition(new GeneratorPosition(i, 0));

                ArrangeChild(itemIndex, child, finalSize);
            }

            return finalSize;
        }

        /// <summary>
        /// Revirtualize items that are no longer visible
        /// </summary>
        /// <param name="minDesiredGenerated">first item index that should be visible</param>
        /// <param name="maxDesiredGenerated">last item index that should be visible</param>
        private void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
        {
            UIElementCollection children = this.InternalChildren;
            IItemContainerGenerator generator = this.ItemContainerGenerator;

            for (int i = children.Count - 1; i >= 0; i--)
            {
                GeneratorPosition childGeneratorPos = new GeneratorPosition(i, 0);
                int itemIndex = generator.IndexFromGeneratorPosition(childGeneratorPos);
                if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
                {
                    generator.Remove(childGeneratorPos, 1);
                    RemoveInternalChildRange(i, 1);
                }
            }
        }

        /// <summary>
        /// When items are removed, remove the corresponding UI if necessary
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
        {
            switch (args.Action)
            {
                case NotifyCollectionChangedAction.Remove:
                case NotifyCollectionChangedAction.Replace:
                case NotifyCollectionChangedAction.Move:
                    RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
                    break;
                case NotifyCollectionChangedAction.Reset:
                    SetVerticalOffset(0); //TODO: Is this enough?
                break;
                    
            }
            base.InvalidateMeasure();
         
        }

        #region Layout specific code
        // I've isolated the layout specific code to this region. If you want to do something other than tiling, this is
        // where you'll make your changes

        /// <summary>
        /// Calculate the extent of the view based on the available size
        /// </summary>
        /// <param name="availableSize">available size</param>
        /// <param name="itemCount">number of data items</param>
        /// <returns></returns>
        private Size CalculateExtent(Size availableSize, int itemCount)
        {
            int childrenPerRow = CalculateChildrenPerRow(availableSize);

            // See how big we are
            return new Size(childrenPerRow * this.ChildSize,
                this.ChildSize * Math.Ceiling((double)itemCount / childrenPerRow));
        }

        /// <summary>
        /// Get the range of children that are visible
        /// </summary>
        /// <param name="firstVisibleItemIndex">The item index of the first visible item</param>
        /// <param name="lastVisibleItemIndex">The item index of the last visible item</param>
        private void GetVisibleRange(out int firstVisibleItemIndex, out int lastVisibleItemIndex)
        {
            int childrenPerRow = CalculateChildrenPerRow(_extent);

            firstVisibleItemIndex = (int)Math.Floor(_offset.Y / this.ChildSize) * childrenPerRow;
            lastVisibleItemIndex = (int)Math.Ceiling((_offset.Y + _viewport.Height) / this.ChildSize) * childrenPerRow - 1;

            ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);
            int itemCount = itemsControl.HasItems ? itemsControl.Items.Count : 0;
            if (lastVisibleItemIndex >= itemCount)
                lastVisibleItemIndex = itemCount - 1;

        }

        /// <summary>
        /// Get the size of the children. We assume they are all the same
        /// </summary>
        /// <returns>The size</returns>
        private Size GetChildSize()
        {
            return new Size(this.ChildSize, this.ChildSize);
        }

        /// <summary>
        /// Position a child
        /// </summary>
        /// <param name="itemIndex">The data item index of the child</param>
        /// <param name="child">The element to position</param>
        /// <param name="finalSize">The size of the panel</param>
        private void ArrangeChild(int itemIndex, UIElement child, Size finalSize)
        {
            int childrenPerRow = CalculateChildrenPerRow(finalSize);

            int row = itemIndex / childrenPerRow;
            int column = itemIndex % childrenPerRow;

            child.Arrange(new Rect(column * this.ChildSize, row * this.ChildSize, this.ChildSize, this.ChildSize));
        }

        /// <summary>
        /// Helper function for tiling layout
        /// </summary>
        /// <param name="availableSize">Size available</param>
        /// <returns></returns>
        private int CalculateChildrenPerRow(Size availableSize)
        {
            // Figure out how many children fit on each row
            int childrenPerRow;
            if (availableSize.Width == Double.PositiveInfinity)
                childrenPerRow = this.Children.Count;
            else
                childrenPerRow = Math.Max(1, (int)Math.Floor(availableSize.Width / this.ChildSize));
            return childrenPerRow;
        }

        #endregion

        #region IScrollInfo implementation
        // See Ben Constable's series of posts at http://blogs.msdn.com/bencon/


        private void UpdateScrollInfo(Size availableSize)
        {
            // See how many items there are
            ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);
            int itemCount = itemsControl.HasItems ? itemsControl.Items.Count : 0;

            Size extent = CalculateExtent(availableSize, itemCount);
            // Update extent
            if (extent != _extent)
            {
                _extent = extent;
                if (_owner != null)
                    _owner.InvalidateScrollInfo();
            }
            //SetVerticalOffset(0);
            // Update viewport
            if (availableSize != _viewport)
            {
                _viewport = availableSize;
                if (_owner != null)
                    _owner.InvalidateScrollInfo();
            }
        }

        public ScrollViewer ScrollOwner
        {
            get { return _owner; }
            set { _owner = value; }
        }

        public bool CanHorizontallyScroll
        {
            get { return _canHScroll; }
            set { _canHScroll = value; }
        }

        public bool CanVerticallyScroll
        {
            get { return _canVScroll; }
            set { _canVScroll = value; }
        }

        public double HorizontalOffset
        {
            get { return _offset.X; }
        }

        public double VerticalOffset
        {
            get { return _offset.Y; }
        }

        public double ExtentHeight
        {
            get { return _extent.Height; }
        }

        public double ExtentWidth
        {
            get { return _extent.Width; }
        }

        public double ViewportHeight
        {
            get { return _viewport.Height; }
        }

        public double ViewportWidth
        {
            get { return _viewport.Width; }
        }

        public void LineUp()
        {
            SetVerticalOffset(this.VerticalOffset - 10);
        }

        public void LineDown()
        {
            SetVerticalOffset(this.VerticalOffset + 10);
        }

        public void PageUp()
        {
            SetVerticalOffset(this.VerticalOffset - _viewport.Height);
        }

        public void PageDown()
        {
            SetVerticalOffset(this.VerticalOffset + _viewport.Height);
        }

        public void MouseWheelUp()
        {
            SetVerticalOffset(this.VerticalOffset - 10);
        }

        public void MouseWheelDown()
        {
            SetVerticalOffset(this.VerticalOffset + 10);
        }

        public void LineLeft()
        {
            throw new InvalidOperationException();
        }

        public void LineRight()
        {
            throw new InvalidOperationException();
        }

        public Rect MakeVisible(Visual visual, Rect rectangle)
        {
            return new Rect();
        }

        public void MouseWheelLeft()
        {
            throw new InvalidOperationException();
        }

        public void MouseWheelRight()
        {
            throw new InvalidOperationException();
        }

        public void PageLeft()
        {
            throw new InvalidOperationException();
        }

        public void PageRight()
        {
            throw new InvalidOperationException();
        }

        public void SetHorizontalOffset(double offset)
        {
            throw new InvalidOperationException();
        }

        public void SetVerticalOffset(double offset)
        {
            if (offset < 0 || _viewport.Height >= _extent.Height)
            {
                offset = 0;
            }
            else
            {
                if (offset + _viewport.Height >= _extent.Height)
                {
                    offset = _extent.Height - _viewport.Height;
                }
            }

            _offset.Y = offset;

            if (_owner != null)
                _owner.InvalidateScrollInfo();

            _trans.Y = -offset;

            // Force us to realize the correct children
            InvalidateMeasure();
        }

        private TranslateTransform _trans = new TranslateTransform();
        private ScrollViewer _owner;
        private bool _canHScroll = false;
        private bool _canVScroll = false;
        private Size _extent = new Size(0, 0);
        private Size _viewport = new Size(0, 0);
        private Point _offset;

        #endregion

    }
}
